
/** 发布订阅 */
/**
 * 发布订阅就好比以前邮局订阅报纸或订购牛奶一样，功能主要有订阅、发布、退订功能
 * 特点：
 * 1、可以实现订阅，只要订阅了，订阅的事件在发布时候都会收到，没有订阅的不会触发
 * 2、退订后，再次发布不会触发
 * 3、退订后可以再次订阅
 */

const postOffice = {} // 定义一个邮局

postOffice.clientList = {} // 定义邮局的报纸类型和订阅的人

postOffice.subscribe = function (key, fn) {// 订阅报纸类型和回调  fn可以是回调的方法或者的订阅的人
  if (!this.clientList[key]) {
    this.clientList[key] = [] // 没有订阅就创建缓存列表
  }
  this.clientList[key].push(fn)
  console.log(`${fn.name}订阅${key}`);
}

postOffice.trigger = function () {
  let key = Array.prototype.shift.call(arguments)  // 获取订阅类型
  let fns = this.clientList[key]

  if (!fns || fns.length === 0) {
    console.log('发布：没人订阅||没有此类型报纸');
    return false // 没有此类型报纸 或没人订阅
  }

  for (const n of fns) {
    n.speak.call(this, arguments)
  }
}
postOffice.remove = function (key, fn) {// 退订报纸
  let fns = this.clientList[key]
  if (!fns || (fns && fns.length === 0)) {
    console.log("退订：没有此类型报纸||没人订阅");
    return false
  } else {
    for (let i = 0; i < fns.length; i++) {
      if (fn === fns[i]) {
        fns.splice(i, 1)
        console.log(`${fn.name}退订了${key}`);
      }
    }
  }
}


function human (name) {
  this.name = name
  this.speak = function () {
    console.log(`订阅：接收到报纸的是${name}`);
  }
  this.unSubscribe = function () {
    console.log(`退订的是${name}`);
  }
}
let personA = new human('小明')
let personD = new human('金角')
let personB = new human('银角')
let personC = new human('小旋风')
let personE = new human('哗哗')


postOffice.subscribe('体育报', personA)
postOffice.subscribe('体育报', personD)
postOffice.subscribe('体育报', personB)
postOffice.subscribe('南方早报', personC)
postOffice.subscribe('今日晨报', personE)

// 发布
postOffice.trigger('体育报')
postOffice.trigger('体育晚报')
postOffice.trigger('南方早报')
postOffice.trigger('今日晨报')
console.log('-----------------退订---------------------------');
// 退订
postOffice.remove('体育报', personC)
postOffice.remove('体育报', personB)

postOffice.trigger('体育报')
console.log('-------------再次订阅-------------------------------');
postOffice.subscribe('体育报', personB)
postOffice.trigger('体育报')




/** 观察者 */
/**
 * 分为观察者和被观察者，观察者可以有多个，被观察者只有一个，被观察者一旦有变化都会告诉观察者或被观察者发现
 * 观察者模式没有发布订阅的退订模式
 */

class Subject {// 被观察者
  constructor() {
    this.state = null
    this.observers = []
  }
  getState () {
    return this.state
  }
  setState (fn) {
    this.state = fn
    this.notifyAllObservers()
  }
  notifyAllObservers () {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
  attach (observer) {
    this.observers.push(observer)
  }
}

// 观察者
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update () {
    console.log(`${this.name} update, state: ${this.subject.getState()}`)
  }
}

// 测试
let s = new Subject()
let ObserverA = new Observer('ObserverA', s)
let ObserverB = new Observer('ObserverB', s)
let doSomething = function () {
  return console.log('发生了变化');
}
s.setState(doSomething)











/** 模拟 vue bus 事件总线 */
/**
 * 实现简单功能
 */
function subscriber () {
  this.eventList = {}
}
subscriber.prototype.emit = function (type, val) {
  this.eventList[type] = val
}

subscriber.prototype.on = function (type, callback) {
  if (!this.eventList[type]) {
    console.log('没有此方法')
    return false
  }

  callback(this.eventList[type])
}
const person = new subscriber();
let word = 'world'
person.emit('onload', 'hello')
person.on('load', (val) => {
  word = val + word
  console.log('新字段：', word);
})
person.on('onload', (val) => {
  word = `${val}  ${word}`
  console.log('新字段：', word);
})
